#!/usr/bin/env bash

## alias
alias cf='cf_help_tree'
alias cf_a_path='echo -e ${PATH//:/\\n}'

## help
# list all modules and functions
function cf_help_tree() {
  for module in $(cf_help_modules); do
    local funcs=($(cf_help_functions ${module#cf_*}))
    local length=${#funcs[@]}

    echo "$module($length)"
    for func in "${funcs[@]}"; do
      if [ "$func" = "${funcs[-1]}" ]; then
        echo "└── $func"
      else
        echo "├── $func"
      fi
    done
  done
}

# list all modules
function cf_help_modules() {
  if _cf_is_bash >/dev/null; then
    declare -F | awk '{print $NF}' | grep "^cf_" | awk -F '_' '{print $1i$2}' i='_' | sort | uniq
  elif _cf_is_zsh >/dev/null; then
    print -l ${(ok)functions} | grep "^cf_" | awk -F '_' '{print $1i$2}' i='_' | sort | uniq
  else
    echo "unsupported shell" && return 1
  fi
}

# list all functions
function cf_help_functions() {
  local module=""
  if [ "x$1" != "x" ] ; then
    local module="$1_"
  fi

  if _cf_is_bash >/dev/null; then
    declare -F | awk '{print $NF}' | grep "^cf_$module" | sort
  elif _cf_is_zsh >/dev/null; then
    print -l ${(ok)functions} | grep "^cf_$module" | sort
  else
    echo "unsupported shell" && return 1
  fi
}

# list all alias
function cf_help_alias() {
  alias | grep cf_a_
}

########################### ps ###########################

function cf_ps_time() {
  local pid=$1
  ! _cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1

  local elapsed="$(ps -oetime= -p $pid | _cf_s_trim)"
  local started="$(ps -olstart= -p $pid | _cf_s_trim)"
  if [ `_cf_is_gnu_date` = "true" ]; then
    started=$(date +'%Y-%m-%d %H:%M:%S' -d "$started")
  fi
  local cpu_time=$(ps -otime= -p $pid | _cf_s_trim)
  echo "started from: $started, elapsed: $elapsed, cumulative cpu time: $cpu_time"
}

function cf_ps_env() {
  local pid=$1
  ! _cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1

  if _cf_is_linux >/dev/null; then
    xargs --null --max-args=1 < /proc/$pid/environ
  elif _cf_is_darwin >/dev/null; then
    ps -wwE -p $pid
  else
    echo "unsupported os" && return 1
  fi
}

function cf_ps_stdio() {
  local pid=$1
  /usr/sbin/lsof -a -p $pid -d 0,1,2
}

function cf_ps_stdout() {
  local pid=$1
  if _cf_is_linux >/dev/null; then
    readlink -f /proc/$pid/fd/1
  elif _cf_is_darwin >/dev/null; then
    /usr/sbin/lsof -a -p $pid -d 1 | awk 'NR>1{print $NF}'
  else
    echo "unsupported os" && return 1
  fi
}

function cf_ps_zombies() {
  ps -opid,state,command -e | awk 'NR==1 || $2=="Z"'
}

########################### os ###########################

function cf_os_dmesg() {
  ! _cf_is_linux >/dev/null && echo "only works in linux" && return 1

  dmesg -T "$@" 2>/dev/null
  [ $? -eq 0 ] && return 0

  dmesg "$@" | perl -w -e 'use strict;
  my ($uptime) = do { local @ARGV="/proc/uptime";<>}; ($uptime) = ($uptime =~ /^(\d+)\./);
  foreach my $line (<>) {
    printf( ($line=~/^\[\s*(\d+)\.\d+\](.+)/) ? ( "[%s]%s\n", scalar localtime(time - $uptime + $1), $2 ) : $line )
  }'
}

# get all ip addresses
function cf_os_iplist() {
  if [ -x /sbin/ip ]; then
    local list=$(/sbin/ip -o -4 addr list | awk '{print $4}' | cut -d'/' -f1 | tr '\n' ',')
  else
    local list=$(/sbin/ifconfig | grep "inet " | awk '{print $2}' | sed 's/addr://' | tr '\n' ',')
  fi
  echo ${list%,}
}

########################### is ###########################

function cf_is_available_port() {
  local port=$1
  if [[ "$OSTYPE" = *linux* ]];then
    local r=$( netstat -ant | awk '$6=="LISTEN" && $4~":'$port'$"' )
  elif [[ "$OSTYPE" = *darwin* ]];then
    local r=$( netstat -ant | awk '$6=="LISTEN"' | grep "\.$port " )
  else
    echo "unknown system" && return 1
  fi

  [ -z "$r" ] && echo "true" && return 0;
  echo "false" && return 1 # port has been used
}

########################### tool alias t ###########################

function cf_t_http_server() {
  local port=${1:-8001}
  python -mSimpleHTTPServer $port 2>/dev/null
}

########################### _cf ###########################

function _cf_trim() {
  local str="$1"
  local extglob=$(_cf_is_extglob_enabled)
  if _cf_is_bash >/dev/null || _cf_is_zsh >/dev/null; then
    [ $extglob = "false" ] && _cf_t_enable_extglob
    str="${str##*( )}"
    str="${str%%*( )}"
    [ $extglob = "false" ] && _cf_t_disable_extglob
  else
    echo "unsupported shell" && return 1
  fi
  echo $str
}

## is
# check current shell is bash
function _cf_is_bash() {
  [[ `_cf_t_shell_name` = "-bash" || `basename $(_cf_t_shell_name)` = "bash" ]] && echo "true" && return 0
  echo "false" && return 1
}

# check current shell is zsh
function _cf_is_zsh() {
  [[ `_cf_t_shell_name` = "-zsh" || `basename $(_cf_t_shell_name)` = "zsh" ]] && echo "true" && return 0
  echo "false" && return 1
}

# check current os is linux
function _cf_is_linux() {
  [[ "$OSTYPE" = *linux* ]] && echo "true" && return 0
  echo "false" && return 1
}

# check current os is mac/darwin
function _cf_is_darwin() {
  [[ "$OSTYPE" = *darwin* ]] && echo "true" && return 0
  echo "false" && return 1
}

# check current os is windows/cygwin
function _cf_is_cygwin() {
  [[ "$OSTYPE" = *cygwin* ]] && echo "true" && return 0
  echo "false" && return 1
}

function _cf_is_gnu_date() {
  date --version >/dev/null 2>&1 && echo "true" && return 0
  echo "false" && return 1
}

function _cf_is_gnu_sed() {
  sed --version >/dev/null 2>&1 && echo "true" && return 0
  echo "false" && return 1
}

function _cf_is_gnu_awk() {
  awk --version | grep GNU >/dev/null && echo "true" && return 0
  echo "false" && return 1
}

function _cf_is_gnu_grep() {
  grep --version | grep GNU >/dev/null && echo "true" && return 0
  echo "false" && return 1
}


# check pid is exists
function _cf_is_pid_exists() {
  local pid=$1
  [ -z "$pid" ] && echo "false" && return 1
  kill -0 $pid >/dev/null 2>&1 && echo "true" && return 0
  echo "false" && return 1
}

# check file name globbing flag
function _cf_is_glob_enabled() {
  if _cf_is_bash >/dev/null; then
    [[ $- != *f* ]] && echo "true" && return 0
  elif _cf_is_zsh >/dev/null; then
    [[ -o glob ]] && echo "true" && return 0
  else
    echo "unsupported shell" && return 1
  fi
  echo "false" && return 1
}

# check extglob flag
function _cf_is_extglob_enabled() {
  if _cf_is_bash >/dev/null; then
    shopt -q extglob && echo "true" && return 0
  elif _cf_is_zsh >/dev/null; then
    [[ -o kshglob ]] && echo "true" && return 0
  else
    echo "unsupported shell" && return 1
  fi
  echo "false" && return 1
}

## string alias s_
# trim string
# shellcheck disable=SC2120
function _cf_s_trim() {
  if [ -p /dev/stdin ]; then
    while IFS='' read line; do
      _cf_trim "$line"
    done
  elif [ $# -eq 1 ]; then
    _cf_trim "$1"
  else
    _cf_trim ""
  fi
}

# java style startsWith
function _cf_s_starts_with() {
  local str=$1
  local pre=$2
  [[ "$str" ==  ${pre}* ]]
}

# java style substring
function _cf_s_substring() {
  local str=$1
  local begin=$2
  local end=$3
  if [ -z "$end" ]; then
    echo ${str:$begin}
  else
    local len=`expr $end - $begin`
    echo ${str:$begin:$len}
  fi
}

## tools alias t_
# get current shell name
function _cf_t_shell_name() {
  local name=$( ps -ocommand= -p $$ | awk '{print $1}')
  if _cf_s_starts_with $name "-"; then
    _cf_s_substring $name 1
  else
    echo $name
  fi
}

# enable file name globbing
function _cf_t_enable_glob() {
  _cf_is_bash >/dev/null && set +f && return 0
  _cf_is_zsh >/dev/null && set -o glob && return 0
  echo "unsupported shell" && return 1
}

# disable file name globbing
function _cf_t_disable_glob() {
  _cf_is_bash >/dev/null && set -f && return 0
  _cf_is_zsh >/dev/null && set -o noglob && return 0
  echo "unsupported shell" && return 1
}

# enable extglob
function _cf_t_enable_extglob() {
  _cf_is_bash >/dev/null && shopt -s extglob && return 0
  _cf_is_zsh >/dev/null && set -o kshglob && return 0
  echo "unsupported shell" && return 1
}

# disable extglob
function _cf_t_disable_extglob() {
  _cf_is_bash >/dev/null && shopt -u extglob && return 0
  _cf_is_zsh >/dev/null && unsetopt kshglob && return 0
  echo "unsupported shell" && return 1
}
